/*
 * © 2024 huayunliufeng保留所有权利, 依据MIT许可证发布。
 * 请勿更改或删除版权声明或此文件头。
 * 此代码是免费软件, 您可以重新分发和/或修改它。
 * 开源是希望它有用, 但不对代码做任何保证。
 * 如有疑问请联系: huayunliufeng@163.com
 */

package io.github.huayunliufeng.dbinfo.dbinfo;

import io.github.huayunliufeng.common.utils.HylfCollectionUtil;
import io.github.huayunliufeng.common.utils.HylfDataUtil;
import io.github.huayunliufeng.common.utils.HylfFunUtil;
import io.github.huayunliufeng.dbinfo.model.*;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 获取数据库信息, 支持同时获取多个数据库信息。
 *
 * @author huayunliufeng
 * @date_time 2024/3/23 13:59
 */
@Slf4j
public class ObtainDbInfo {

    public static final String CATALOG = "CATALOG";
    public static final String SCHEMA = "SCHEMA";
    public static final String TABLE_CAT = "TABLE_CAT";
    /**
     * 每个新的连接一个ObtainDbInfo对象
     */
    private static Map<Connection, ObtainDbInfo> obtainDbInfoMap = null;

    /**
     * 每个ObtainDbInfo对象存储一个DataBaseInfo
     */
    private static Map<ObtainDbInfo, DataBaseInfo> dataBaseInfoMap = null;
    protected Connection conn;
    protected DatabaseMetaData databaseMetaData;
    /**
     * 每个ObtainDbInfo对象保存的属性
     */
    private Map<String, Object> obtainDbInfoPropMap = null;
    private Map<String, List<UdtInfo>> udtInfoMap = null;
    private Map<String, List<AttributeInfo>> attributeInfoMap = null;
    private Map<String, List<SchemaInfo>> schemaInfoMap = null;
    private Map<String, List<SuperTypesInfo>> superTypesInfoMap = null;

    private List<String> catalogList = null;
    private List<ClientInfoProperties> clientInfoPropertiesList = null;
    private List<SchemaInfo> schemaList = null;
    private List<TypeInfo> typeInfoList = null;

    protected ObtainDbInfo(Connection conn) {
        if (conn == null) {
            throw new RuntimeException("数据库连接不能为空。");
        }
        this.conn = conn;
        this.databaseMetaData = HylfFunUtil.methodVoidReturnExec(conn::getMetaData);
    }

    public static ObtainDbInfo build(Connection conn) {
        obtainDbInfoMap = HylfFunUtil.mapAddValue(obtainDbInfoMap, conn, () -> new ObtainDbInfo(conn));
        return obtainDbInfoMap.get(conn);
    }

    /**
     * <p>检索DatabaseMetaData对象，该对象包含有关此Connection对象表示连接到的数据库的元数据。</p>
     * <p>元数据包括关于数据库的表、它支持的SQL语法、它的存储过程、这个连接的功能等等的信息。</p>
     *
     * @return 数据库信息
     */
    public DataBaseInfo getDataBaseInfo() {
        dataBaseInfoMap = HylfFunUtil.mapAddValue(dataBaseInfoMap,
                this,
                () -> HylfDataUtil.methodToJavaBean(databaseMetaData, DataBaseInfo.class));
        return dataBaseInfoMap.get(this);
    }

    /**
     * 检索此Connection对象的当前编目名称。
     *
     * @return catalog
     */
    public String getCatalog() {
        return HylfFunUtil.methodVoidReturnExec(() -> {
            obtainDbInfoPropMap = HylfFunUtil.mapAddValue(obtainDbInfoPropMap, CATALOG,
                    () -> HylfFunUtil.methodVoidReturnExec(conn::getCatalog, "获取catalog失败。"));
            return (String) obtainDbInfoPropMap.get(CATALOG);
        });
    }

    /**
     * 检索此Connection对象的当前架构名称。
     *
     * @return schema
     */
    public String getSchema() {
        return HylfFunUtil.methodVoidReturnExec(() -> {
            obtainDbInfoPropMap = HylfFunUtil.mapAddValue(obtainDbInfoPropMap, SCHEMA,
                    () -> HylfFunUtil.methodVoidReturnExec(conn::getSchema, "获取schema失败。"));
            return (String) obtainDbInfoPropMap.get(SCHEMA);
        });
    }

    /**
     * <p>检索特定模式中定义的用户定义类型(UDTs)的描述。特定于模式的udt可能具有JAVA_OBJECT、STRUCT或DISTINCT类型。</p>
     * <p>只返回与目录、模式、类型名称和类型条件匹配的类型。它们按DATA_TYPE、TYPE_CAT、TYPE_SCHEMA和TYPE_NAME排序。</p>
     * <p>类型名称参数可以是完全限定名称。在这种情况下，忽略catalog和schemaPattern参数。</p>
     * <p>如果驱动程序不支持UDTs，则返回空结果集。</p>
     *
     * @param catalog         目录名称;必须与存储在数据库中的目录名称匹配;“”检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围
     * @param schemaPattern   模式模式名称;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param typeNamePattern 类型名称模式;必须匹配存储在数据库中的类型名称;可能是一个完全限定的名字
     * @param types           要包含的用户定义类型(JAVA_OBJECT、STRUCT或DISTINCT)的列表;null返回所有类型
     * @return 每行描述一个UDT
     */
    public List<UdtInfo> getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) {
        String logFormat = "获取数据库用户定义类型(UDTs)信息失败。[catalog = {}, schemaPattern = {}, typeNamePattern = {}, types = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schemaPattern, typeNamePattern);
            udtInfoMap = HylfFunUtil.mapAddValue(udtInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getUDTs(catalog, schemaPattern, typeNamePattern, types)
                        , logFormat, catalog, schemaPattern, typeNamePattern, types);
                List<UdtInfo> exportedKeyInfoList = HylfDataUtil.autoSetValue(resultSet, UdtInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return exportedKeyInfoList;
            });
            return udtInfoMap.get(key);
        });
    }

    /**
     * <p>检索特定模式中定义的用户定义类型(UDTs)的描述。特定于模式的UDT可能具有JAVA_OBJECT、STRUCT或DISTINCT类型。</p>
     * <p>只返回与目录、模式、类型名称和类型条件匹配的类型。它们按DATA_TYPE、TYPE_CAT、TYPE_SCHEMA和TYPE_NAME排序。</p>
     * <p>类型名称参数可以是完全限定名称。在这种情况下，忽略catalog和schemaPattern参数。</p>
     * <p>如果驱动程序不支持UDTs，则返回空结果集。</p>
     *
     * @param typeNamePattern 类型名称模式;必须匹配存储在数据库中的类型名称;可能是一个完全限定的名字
     * @return 每行描述一个UDT
     */
    public List<UdtInfo> getUDTs(String typeNamePattern) {
        return getUDTs(getCatalog(), getSchema(), typeNamePattern, null);
    }

    /**
     * 检索在此数据库中的特定模式中定义的用户定义类型(UDT)层次结构的描述。只对直接的父类型/子类型关系建模。
     *
     * @param catalog         目录名称;“”检索那些没有目录的;null表示从选择标准中删除目录名称
     * @param schemaPattern   模式名称模式;“”检索那些没有模式的
     * @param typeNamePattern UDT名称模式;可能是一个完全限定的名字
     * @return List 提供有关指定UDT的信息
     */
    public List<SuperTypesInfo> getSuperTypes(String catalog, String schemaPattern,
                                              String typeNamePattern) {
        String logFormat = "获取数据库SuperTypes失败。[catalog = {}, schemaPattern = {}, typeNamePattern = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schemaPattern, typeNamePattern);
            superTypesInfoMap = HylfFunUtil.mapAddValue(superTypesInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getSuperTypes(catalog, schemaPattern, typeNamePattern)
                        , logFormat, catalog, schemaPattern, typeNamePattern
                );
                List<SuperTypesInfo> superTypesInfoList = HylfDataUtil.autoSetValue(resultSet, SuperTypesInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return superTypesInfoList;
            });
            return superTypesInfoMap.get(key);
        });

    }

    /**
     * 检索在此数据库中的特定模式中定义的用户定义类型(UDT)层次结构的描述。只对直接的父类型/子类型关系建模。
     *
     * @param typeNamePattern UDT名称模式;可能是一个完全限定的名字
     * @return List 提供有关指定UDT的信息
     */
    public List<SuperTypesInfo> getSuperTypes(String typeNamePattern) {
        return getSuperTypes(getCatalog(), getSchema(), typeNamePattern);
    }

    /**
     * 检索给定模式和编目中可用的用户定义类型(UDT)的给定类型的给定属性的描述。
     *
     * @param catalog              目录名称;必须与存储在数据库中的目录名称匹配;“”检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围
     * @param schemaPattern        模式名称模式;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param typeNamePattern      类型名称模式;必须匹配存储在数据库中的类型名称
     * @param attributeNamePattern 属性名模式;必须匹配在数据库中声明的属性名
     * @return 每一行是一个属性描述
     */
    public List<AttributeInfo> getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) {
        String logFormat = "获取数据库AttributeInfo失败。[catalog = {}, schemaPattern = {}, typeNamePattern = {}, attributeNamePattern = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schemaPattern, typeNamePattern);
            attributeInfoMap = HylfFunUtil.mapAddValue(attributeInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getAttributes(catalog, schemaPattern, typeNamePattern, attributeNamePattern)
                        , logFormat, catalog, schemaPattern, typeNamePattern, attributeNamePattern);
                List<AttributeInfo> attributeInfoList = HylfDataUtil.autoSetValue(resultSet, AttributeInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return attributeInfoList;
            });
            return attributeInfoMap.get(key);
        });
    }

    /**
     * 检索给定模式和编目中可用的用户定义类型(UDT)的给定类型的给定属性的描述。
     *
     * @param typeNamePattern      类型名称模式;必须匹配存储在数据库中的类型名称
     * @param attributeNamePattern 属性名模式;必须匹配在数据库中声明的属性名
     * @return 每一行是一个属性描述
     */
    public List<AttributeInfo> getAttributes(String typeNamePattern, String attributeNamePattern) {
        return getAttributes(getCatalog(), getSchema(), typeNamePattern, attributeNamePattern);
    }

    /**
     * 检索此数据库中可用的目录名称。结果按目录名称排序。
     *
     * @return 每行有一个String列，该列是一个目录名
     */
    public List<String> getCatalogs() {
        String logFormat = "获取数据库catalog失败。";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            if (HylfCollectionUtil.isNotEmpty(catalogList)) {
                return catalogList;
            }
            catalogList = new ArrayList<>();
            ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(databaseMetaData::getCatalogs, logFormat);
            while (resultSet != null && resultSet.next()) {
                catalogList.add(resultSet.getString(TABLE_CAT));
            }
            HylfFunUtil.autoClose(resultSet);
            return catalogList;
        });
    }

    /**
     * 检索此数据库中可用的目录名称。结果按目录名称排序。
     *
     * @param ignoreCatalogs 忽略的catalog
     * @return 每行有一个String列，该列是一个目录名
     */
    public String[] getCatalogsToArray(String... ignoreCatalogs) {
        if (HylfCollectionUtil.isEmpty(catalogList)) {
            catalogList = getCatalogs();
        }
        return HylfCollectionUtil.filterListToArray(catalogList, String.class, ignoreCatalogs);
    }

    /**
     * 检索此数据库中可用的模式名称。结果按TABLE_CATALOG和table_schema排序。
     *
     * @param catalog       目录名称;必须与存储在数据库中的目录名称匹配;"“检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围。
     * @param schemaPattern 模式名;必须与存储在数据库中的模式名称匹配;null表示不应该使用模式名来缩小搜索范围。
     * @return 每一行都是一个模式描述
     */
    public List<SchemaInfo> getSchemas(String catalog, String schemaPattern) {
        String logFormat = "获取数据库模式失败。[catalog = {}, schemaPattern = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog);
            schemaInfoMap = HylfFunUtil.mapAddValue(schemaInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getSchemas(catalog, schemaPattern)
                        , logFormat, catalog, schemaPattern);
                List<SchemaInfo> schemaInfoList = HylfDataUtil.autoSetValue(resultSet, SchemaInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return schemaInfoList;
            });
            return schemaInfoMap.get(key);
        });
    }

    /**
     * 检索此数据库中可用的模式名称。结果按TABLE_CATALOG和table_schema排序。
     *
     * @return 每一行都是一个模式描述
     */
    public List<SchemaInfo> getSchemas() {
        String logFormat = "获取数据库模式失败。";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            if (HylfCollectionUtil.isNotEmpty(schemaList)) {
                return schemaList;
            }
            ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(databaseMetaData::getSchemas, logFormat);
            schemaList = HylfDataUtil.autoSetValue(resultSet, SchemaInfo.class);
            HylfFunUtil.autoClose(resultSet);
            return schemaList;
        });
    }

    /**
     * 检索驱动程序支持的客户端信息属性列表。
     *
     * @return 每一行都是一个受支持的客户端信息属性
     */
    public List<ClientInfoProperties> getClientInfoProperties() {
        String logFormat = "获取驱动程序支持的客户端信息属性列表失败。";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            if (HylfCollectionUtil.isNotEmpty(clientInfoPropertiesList)) {
                return clientInfoPropertiesList;
            }
            ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(databaseMetaData::getClientInfoProperties, logFormat);
            clientInfoPropertiesList = HylfDataUtil.autoSetValue(resultSet, ClientInfoProperties.class);
            HylfFunUtil.autoClose(resultSet);
            return clientInfoPropertiesList;
        });
    }

    /**
     * <p>检索此数据库支持的所有数据类型的描述。它们按DATA_TYPE排序，然后按数据类型映射到相应JDBC SQL类型的紧密程度排序。</p>
     * <p>如果数据库支持SQL不同类型，那么getTypeInfo()将返回一个单独的行，其中TYPE_NAME为DISTINCT, DATA_TYPE为types.distinct。</p>
     * <p>如果数据库支持SQL结构化类型，那么getTypeInfo()将返回一个单独的行，其中TYPE_NAME为STRUCT, DATA_TYPE为Types.STRUCT。</p>
     * <p>如果支持SQL不同类型或结构化类型，则可以从getUDTs()方法获得有关各个类型的信息。</p>
     *
     * @return 每一行都是一个SQL类型描述
     */
    public List<TypeInfo> getTypeInfo() {
        String logFormat = "获取此数据库支持的所有数据类型失败。";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            if (HylfCollectionUtil.isNotEmpty(typeInfoList)) {
                return typeInfoList;
            }
            ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(databaseMetaData::getTypeInfo, logFormat);
            typeInfoList = HylfDataUtil.autoSetValue(resultSet, TypeInfo.class);
            HylfFunUtil.autoClose(resultSet);
            return typeInfoList;
        });
    }
}
